package com.atguigu.gmall.search.service.impl;
import com.atguigu.gmall.search.vo.SearchRespVo.OrderMap;
import com.google.common.collect.Lists;

import com.atguigu.gmall.search.Goods;
import com.atguigu.gmall.search.repo.GoodsRepository;
import com.atguigu.gmall.search.service.SearchService;
import com.atguigu.gmall.search.vo.SearchParamVo;
import com.atguigu.gmall.search.vo.SearchRespVo;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilder;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.nested.NestedAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.document.Document;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.data.elasticsearch.core.query.UpdateQuery;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

@Service
public class SearchServiceImpl implements SearchService {

    @Autowired
    GoodsRepository goodsRepository;
    @Autowired
    ElasticsearchRestTemplate elasticsearchRestTemplate;//复杂查询

    //每页显示数量
    int pageSize = 10;

    @Override
    public SearchRespVo search(SearchParamVo searchParamVo) {

        // 检索
        //1 构建查询条件
        Query query = this.buildQuery(searchParamVo);

        //2 查询得到结果
        SearchHits<Goods> result = elasticsearchRestTemplate.search(query,
                Goods.class, //返回值类型
                IndexCoordinates.of("goods"));//哪个索引进行查询

        //3 todo 根据查询的结果，构建前端需要的返回结果
        SearchRespVo respVo= this.buildSearchResp(result,searchParamVo);

        return respVo;
    }


    //3 根据查询的结果，【构建前端需要的返回结果】
    private SearchRespVo buildSearchResp(SearchHits<Goods> result, SearchParamVo searchParamVo) {
        //前端希望返回的对象
        SearchRespVo respVo = new SearchRespVo();
        //1 检索参数
        respVo.setSearchParam(searchParamVo);

        //2 品牌面包屑
        if (!StringUtils.isEmpty(searchParamVo.getTrademark())){
            //trademark=3:华为==》品牌:华为
            respVo.setTrademarkParam("品牌:"+ searchParamVo.getTrademark().split(":")[1]);
        }

        //3 属性面包屑
        if (searchParamVo.getProps()!=null && searchParamVo.getProps().length>0){
            //遍历多个props
            //props=4:256GB:机身存储==>机身存储:256GB
            List<SearchRespVo.Props> propsList = Arrays.stream(searchParamVo.getProps())
                    .map((item) -> {
                        String[] split = item.split(":");
                        SearchRespVo.Props props = new SearchRespVo.Props();
                        props.setAttrId(Long.parseLong(split[0]));
                        props.setAttrName(split[2]);
                        props.setAttrValue(split[1]);
                        return props;
                    })
                    .collect(Collectors.toList());
            respVo.setPropsParamList(propsList);
        }

        //4  品牌列表：聚合(分组) aggregations
        //trademark=3:华为
        ParsedLongTerms tmIdAgg = result.getAggregations().get("tmIdAgg");//按照 tmId 进行分组聚合
        List<SearchRespVo.Trademark> trademarkList = tmIdAgg.getBuckets().stream()
                .map((item) -> { //每个桶 bucket ，即商品
                    SearchRespVo.Trademark trademark = new SearchRespVo.Trademark();
                    //品牌id
                    long tmId = item.getKeyAsNumber().longValue();//获取桶的名字，作为long类型返回
                    trademark.setTmId(tmId);
                    //品牌name
                    ParsedStringTerms tmNameAgg = item.getAggregations().get("tmNameAgg");
                    String tmName = tmNameAgg.getBuckets().get(0).getKeyAsString();
                    trademark.setTmName(tmName);
                    //品牌logo
                    ParsedStringTerms tmLogoUrlAgg = item.getAggregations().get("tmLogoUrlAgg");
                    String tmLogoUrl = tmLogoUrlAgg.getBuckets().get(0).getKeyAsString();
                    trademark.setTmLogoUrl(tmLogoUrl);

                    return trademark;
                })
                .collect(Collectors.toList());
        respVo.setTrademarkList(trademarkList);

        //5  属性列表：聚合(分组) aggregations
        //props=4:256GB:机身存储 props=23:8G:运行内存
        ParsedNested attrAgg = result.getAggregations().get("attrAgg");//属性聚合
        //按照 attrIdAgg 进行聚合
        ParsedLongTerms attrIdAgg = attrAgg.getAggregations().get("attrIdAgg");

        List<SearchRespVo.Attrs> attrsList = attrIdAgg.getBuckets().stream()
                .map((bucket) -> {
                    SearchRespVo.Attrs attrs = new SearchRespVo.Attrs();
                    //属性id
                    long attrId = bucket.getKeyAsNumber().longValue();
                    attrs.setAttrId(attrId);
                    //属性name
                    ParsedStringTerms attrNameAgg = bucket.getAggregations().get("attrNameAgg");
                    String attrName = attrNameAgg.getBuckets().get(0).getKeyAsString();
                    attrs.setAttrName(attrName);
                    //value
                    ParsedStringTerms attrValueAgg = bucket.getAggregations().get("attrValueAgg");
                    List<String> attrValueList = attrValueAgg.getBuckets().stream()
                            .map((item) -> item.getKeyAsString())
                            .collect(Collectors.toList());
                    attrs.setAttrValueList(attrValueList);

                    return attrs;
                })
                .collect(Collectors.toList());

        respVo.setAttrsList(attrsList);

        //6 url参数：/list.html?category3Id=61&trademark=3:华为&props=4:256GB:机身存储&props=23:8G:运行内存&order=2:desc&pageNo=1&keyword=小米
        String urlParam = buildUrlParam(searchParamVo);
        respVo.setUrlParam(urlParam);

        //7 排序
        if (!StringUtils.isEmpty(searchParamVo.getOrder())){
            OrderMap orderMap = new OrderMap();
            //2:desc
            String[] split = searchParamVo.getOrder().split(":");
            orderMap.setSort(split[1]);
            orderMap.setType(split[0]);
            respVo.setOrderMap(orderMap);
        }

        //8 商品列表 Goods  在查询到的结果里面，有命中的结果hits，hits里还有一个hits
        List<Goods> goodsList = result.getSearchHits()//获取所有命中的记录
                .stream()
                .map((item) -> {
                    Goods content = item.getContent();//所有商品 content=>good

                    // 模糊高亮显示
                    if (!StringUtils.isEmpty(searchParamVo.getKeyword())){
                        String newTitle = item.getHighlightField("title").get(0);
                        content.setTitle(newTitle);
                    }

                    return content;
                })
                .collect(Collectors.toList());
        respVo.setGoodsList(goodsList);

        //9 页码
        respVo.setPageNo(searchParamVo.getPageNo());

        //10 总页码 : 总记录数%每页数量 == 0 ？ 总记录数%每页数量 ： 总记录数%每页数量+1
        long totalHits = result.getTotalHits();
        long totalPages = totalHits % pageSize == 0 ? totalHits % pageSize : totalHits % pageSize + 1;
        respVo.setTotalPages(totalPages);

        return respVo;
    }

    //根据前端带来的参数，构建原来的url
    ///list.html?category3Id=61&trademark=3:华为&props=4:256GB:机身存储&props=23:8G:运行内存&order=2:desc&pageNo=1&keyword=小米
    private String buildUrlParam(SearchParamVo searchParamVo) {
        StringBuilder builder = new StringBuilder("list.html?");
        //三级分类参数
        if (searchParamVo.getCategory1Id()!=null){
            builder.append("&category1Id="+searchParamVo.getCategory1Id());
        }
        if (searchParamVo.getCategory2Id()!=null){
            builder.append("&category2Id="+searchParamVo.getCategory2Id());
        }
        if (searchParamVo.getCategory3Id()!=null){
            builder.append("&category3Id="+searchParamVo.getCategory3Id());
        }
        //关键字
        if (!StringUtils.isEmpty(searchParamVo.getKeyword())){
            builder.append("&keyword="+searchParamVo.getKeyword());
        }
        //品牌
        if (!StringUtils.isEmpty(searchParamVo.getTrademark())){
            builder.append("&trademark="+searchParamVo.getTrademark());
        }
        //属性
        if (searchParamVo.getProps()!=null && searchParamVo.getProps().length>0){
            //遍历
            Arrays.stream(searchParamVo.getProps())
                    .forEach((item)->{
                        builder.append("&props="+item);
                    });

        }
        return builder.toString();
    }

    //1 根据前端传递的请求参数，构建 检索使用的查询条件 【DSL拼装】  构建查询条件===>参考检索.json,SearchParamVo
    private Query buildQuery (SearchParamVo searchParamVo){

        //==========查询开始==========
        //1 查询条件
        BoolQueryBuilder bool = QueryBuilders.boolQuery();
        //构造bool的查询条件
        //1.1 一级分类查询
        if (searchParamVo.getCategory1Id()!=null){
            TermQueryBuilder termQuery = QueryBuilders.termQuery("category1Id", searchParamVo.getCategory1Id());
            bool.must(termQuery);
        }//1.2 二级分类查询
        if (searchParamVo.getCategory2Id()!=null){
            TermQueryBuilder termQuery = QueryBuilders.termQuery("category2Id", searchParamVo.getCategory2Id());
            bool.must(termQuery);
        }//1.3 三级分类查询
        if (searchParamVo.getCategory3Id()!=null){
            TermQueryBuilder termQuery = QueryBuilders.termQuery("category3Id", searchParamVo.getCategory3Id());
            bool.must(termQuery);
        }
        //1.4 品牌查询
        if (!StringUtils.isEmpty(searchParamVo.getTrademark())){
            String[] split = searchParamVo.getTrademark().split(":");//trademark=3:华为
            TermQueryBuilder termQuery = QueryBuilders.termQuery("tmId", split[0]);
            bool.must(termQuery);
        }
        //1.5 关键字查询
        if (!StringUtils.isEmpty(searchParamVo.getKeyword())) {
            MatchQueryBuilder matchQuery = QueryBuilders.matchQuery("title", searchParamVo.getKeyword());
            bool.must(matchQuery);
        }
        //1.6 属性查询
        if (searchParamVo.getProps()!=null && searchParamVo.getProps().length>0) {
            Arrays.stream(searchParamVo.getProps())
                    .forEach((searchAttr)->{
                        // props=4:256GB:机身存储  props=23:8G:运行内存
                        String[] split = searchAttr.split(":");
                        //属性id
                        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
                        TermQueryBuilder query1 = QueryBuilders.termQuery("attrs.attrId", split[0]);
                        boolQuery.must(query1);
                        //属性value
                        TermQueryBuilder query2 = QueryBuilders.termQuery("attrs.attrValue", split[1]);
                        boolQuery.must(query2);
                        NestedQueryBuilder nestedQuery = QueryBuilders.nestedQuery("attrs", boolQuery, ScoreMode.None);
                        bool.must(nestedQuery);
                    });
        }
        //==========查询结束==========

        //2 创建原生query
        NativeSearchQuery query = new NativeSearchQuery(bool);

        //==========排序开始==========
        //1.7 排序方式
        if (!StringUtils.isEmpty(searchParamVo.getOrder())) {
            //order=2:desc 1:asc
            String[] split = searchParamVo.getOrder().split(":");//分割
            Sort.Direction direction = "asc".equals(split[1]) ? Sort.Direction.ASC : Sort.Direction.DESC;//排序方式
            Sort sort = null;
            switch (split[0]){
                case "1":sort = Sort.by(direction, "hotScore");
                    break;
                case "2":sort = Sort.by(direction, "price");
                    break;
                default:sort = Sort.by(Sort.Direction.DESC,"hotScore");
            }
            query.addSort(sort);
        }
        //==========排序结束==========

        //==========分页开始==========
        Pageable pageable = PageRequest.of(searchParamVo.getPageNo()-1, pageSize);
        query.setPageable(pageable);
        //==========分页结束==========

        //==========聚合(品牌)开始==========
        //品牌id聚合
        TermsAggregationBuilder tmIdAgg = AggregationBuilders.terms("tmIdAgg").field("tmId").size(200);
        //品牌name聚合
        TermsAggregationBuilder tmNameAgg = AggregationBuilders.terms("tmNameAgg").field("tmName").size(1);
        tmIdAgg.subAggregation(tmNameAgg);
        //品牌logo聚合
        TermsAggregationBuilder tmLogoUrlAgg = AggregationBuilders.terms("tmLogoUrlAgg").field("tmLogoUrl").size(1);
        tmIdAgg.subAggregation(tmLogoUrlAgg);

        query.addAggregation(tmIdAgg);
        //==========聚合(品牌)结束==========

        //==========聚合(属性)开始==========
        NestedAggregationBuilder attrAgg = AggregationBuilders.nested("attrAgg", "attrs");
        //属性id聚合
        TermsAggregationBuilder attrIdAgg = AggregationBuilders.terms("attrIdAgg").field("attrs.attrId").size(200);
        attrAgg.subAggregation(attrIdAgg);
        //属性name聚合,attrIdAgg的子聚合
        TermsAggregationBuilder attrNameAgg = AggregationBuilders.terms("attrNameAgg").field("attrs.attrName").size(1);
        attrIdAgg.subAggregation(attrNameAgg);
        //属性value聚合
        TermsAggregationBuilder attrValueAgg = AggregationBuilders.terms("attrValueAgg").field("attrs.attrValue").size(100);
        attrIdAgg.subAggregation(attrValueAgg);

        query.addAggregation(attrAgg);
        //==========聚合(属性)结束==========
        return query;
    }

    @Override
    public void up(Goods goods) {
        goodsRepository.save(goods);
    }

    @Override
    public void down(Long skuId) {
        goodsRepository.deleteById(skuId);
    }

    @Override
    public void updateHotScore(Long skuId, Long score) {
        //增加热度分
        Document document = Document.create();
        document.put("hotScore", score);//更新的字段属性

        UpdateQuery updateQuery = UpdateQuery//更新的条件
                .builder(""+skuId)
                .withDocAsUpsert(true)//以更新的方式增加
                .withDocument(document)//更新的文件
                .build();
        elasticsearchRestTemplate.update(updateQuery, IndexCoordinates.of("goods"));//（更新的条件，更新的索引）
    }
}
